Repository navigation
tools(parity): stop the scan walking into build output - #2259
Merged
Merged
Conversation
Every glob in the ledger ends in `**`, and `**` descends into a build directory exactly as happily as into a source tree. A SwiftPM dependency checked out under `Packages/StrandAnalytics/.build/checkouts/` lays itself out with a `Sources` directory, by convention, and therefore matches `Packages/**/Sources/**/*.swift` as squarely as a first-party file does. On this machine that was 3,543 files out of 6,039 matched: 59 percent of what the scanner read was somebody else's vendored library. WHY IT IS WORSE THAN NOISE The pollution is SUBTRACTIVE. It does not add findings a reader would question, it REMOVES them, by handing a declaration a callsite that exists only in a vendored copy. That is not hypothetical. A scan on a working tree came back one test-only-callsite short for Packages/StrandAnalytics against a baseline derived on a clean checkout, and the difference was GRDB's SPM smoke test at Tests/SPM/PlainPackage/Sources/SPM/main.swift calling read, write and execute. The local acceptance test passed anyway, because both sides of its comparison came from the same polluted tree. CI checks out clean, so it never saw any of it and could not warn. The count went 44 to 43: a finding vanished rather than a spurious one appearing, which is the direction nobody goes looking for. THE CHANGE One filter in `_paths`, which is the only place this module globs: the declaration scan, the reference scan and both callsite corpora all come through it. Paths inside `.build`, `.swiftpm`, `DerivedData`, `build`, `.gradle`, `node_modules` or `Pods` are dropped. CI OUTPUT IS UNCHANGED BY CONSTRUCTION CI already checks out clean, so it was always seeing the filtered numbers. This makes a working tree agree with CI, not the other way round. Measured both ways on the same commit: polluted tree, filtered 266 / 234 / 1135 / 1353 / 2496 pristine checkout 266 / 234 / 1135 / 1353 / 2496 for SWIFT, KOTLIN, PRODUCTION, TEST and REFERENCE globs respectively. THE INVARIANT IS TESTED, NOT ASSERTED Five tests. The one that matters checks the safety property against git rather than against my judgement: everything the filter drops is untracked, so no file the repository actually contains can be hidden by it. Here that is 3,543 dropped and 0 tracked. The others pin the exact shape that caused this (a dependency checkout whose own layout puts `Sources` under `.build`), the second copy SwiftPM writes under `index-build`, Gradle output for the day KSP emits Kotlin rather than Java, and a negative case: `build` is matched as a whole directory component only, so `Sources/buildsystem/build.swift` stays in the scan. NOT IN THIS CHANGE SWIFT_EXCLUDED_GLOBS and KOTLIN_EXCLUDED_GLOBS are still dead constants, referenced nowhere but their own definitions. They document which packages are deliberately out of scope and wiring them up would change what is scanned, so that belongs in its own change with its own re-derive. Governance suite 129 tests, 0 failures.
ryanbr
added a commit
that referenced
this pull request
Sep 19, 2026
…product commits
`parity_ratchet.py` has failed on clean main since the twin map was last written:
ERROR: Tools/parity_twin_map.json: base authority cannot be reproduced with the
current derivation; migration required (see --migrate-authority)
The message points at the derivation. Measured, the derivation is innocent: running
the CURRENT scanner against the tree the map was written from (b6e4e3e) reproduces
the stored manifest EXACTLY, +0 across all twelve sets. So the two scanner changes
that landed since (#2259 stopped the scan walking into build output, #2267 stopped
an unparseable argument erasing a callsite) alter what the scanner would find in
general, but change nothing this repository actually contains.
What moved is the TREE. Three in-scope commits landed after the map: #2322
(AlarmReadback), #2307 (the Oura auth handshake) and #2260 (the WHOOP sports).
Re-derived with the guarded workflow, `--refresh-derived --migrate-authority`, which
writes the candidates, re-runs the ratchet against them, and restores the originals
unless it accepts. Migration waives only the stored manifest's reproducibility, never
a semantic finding, so undeclared one-sided debt would still have failed here.
Every count now reconciles against those commits rather than being asserted:
files 500 -> 502 the two new OuraAuthWatchdog files (#2307)
file_pairs 66 -> 67 OuraDriver is now resolvable as a twin pair
functions 4449 -> 4453 +1 pair (2) + 2 unpaired = 4
function_pairs 173 -> 174
unpaired_funcs 4109 -> 4111
constants 1943 -> 1945 both unpaired
unpaired_consts 591 -> 593
unpaired_files 386 -> 386 count held, membership changed
That last one is the interesting row. OuraDriver.swift and OuraDriver.kt LEFT the
unpaired set (hence the extra file pair) while OuraAuthWatchdog.swift and
OuraAuthWatchdog.kt JOINED it: #2307 added a twin on both platforms without a
declared twin reference, so the ledger does not yet know the two mirror each other.
Not debt by the scanner's own reckoning, and left as-is here rather than bundled
into a re-derivation, but worth a look.
`parity_ledger_baseline.json` is untouched. The 300 known findings are identical, so
nothing was added or forgiven.
This is the drift mode parity-governance.yml's own header documents, including that
it has landed on an outside contributor's PR three times. The daily schedule on main
exists to catch it; this is the maintainer action it asks for.
ryanbr
added a commit
that referenced
this pull request
Sep 19, 2026
… commits (#2325) chore(parity): re-derive the twin-map authority after three in-scope product commits `parity_ratchet.py` has failed on clean main since the twin map was last written: ERROR: Tools/parity_twin_map.json: base authority cannot be reproduced with the current derivation; migration required (see --migrate-authority) The message points at the derivation. Measured, the derivation is innocent: running the CURRENT scanner against the tree the map was written from (b6e4e3e) reproduces the stored manifest EXACTLY, +0 across all twelve sets. So the two scanner changes that landed since (#2259 stopped the scan walking into build output, #2267 stopped an unparseable argument erasing a callsite) alter what the scanner would find in general, but change nothing this repository actually contains. What moved is the TREE. Three in-scope commits landed after the map: #2322 (AlarmReadback), #2307 (the Oura auth handshake) and #2260 (the WHOOP sports). Re-derived with the guarded workflow, `--refresh-derived --migrate-authority`, which writes the candidates, re-runs the ratchet against them, and restores the originals unless it accepts. Migration waives only the stored manifest's reproducibility, never a semantic finding, so undeclared one-sided debt would still have failed here. Every count now reconciles against those commits rather than being asserted: files 500 -> 502 the two new OuraAuthWatchdog files (#2307) file_pairs 66 -> 67 OuraDriver is now resolvable as a twin pair functions 4449 -> 4453 +1 pair (2) + 2 unpaired = 4 function_pairs 173 -> 174 unpaired_funcs 4109 -> 4111 constants 1943 -> 1945 both unpaired unpaired_consts 591 -> 593 unpaired_files 386 -> 386 count held, membership changed That last one is the interesting row. OuraDriver.swift and OuraDriver.kt LEFT the unpaired set (hence the extra file pair) while OuraAuthWatchdog.swift and OuraAuthWatchdog.kt JOINED it: #2307 added a twin on both platforms without a declared twin reference, so the ledger does not yet know the two mirror each other. Not debt by the scanner's own reckoning, and left as-is here rather than bundled into a re-derivation, but worth a look. `parity_ledger_baseline.json` is untouched. The 300 known findings are identical, so nothing was added or forgiven. This is the drift mode parity-governance.yml's own header documents, including that it has landed on an outside contributor's PR three times. The daily schedule on main exists to catch it; this is the maintainer action it asks for.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every glob in the ledger ends in
**, and**descends into a build directory exactlyas happily as into a source tree. A SwiftPM dependency checked out under
Packages/StrandAnalytics/.build/checkouts/lays itself out with aSourcesdirectory,by convention, and therefore matches
Packages/**/Sources/**/*.swiftas squarely as afirst-party file does.
On this machine that was 3,543 files out of 6,039 matched: 59 percent of what the
scanner read was somebody else's vendored library.
WHY IT IS WORSE THAN NOISE
The pollution is SUBTRACTIVE. It does not add findings a reader would question, it
REMOVES them, by handing a declaration a callsite that exists only in a vendored copy.
That is not hypothetical. A scan on a working tree came back one test-only-callsite
short for Packages/StrandAnalytics against a baseline derived on a clean checkout, and
the difference was GRDB's SPM smoke test at Tests/SPM/PlainPackage/Sources/SPM/main.swift
calling read, write and execute. The local acceptance test passed anyway, because both
sides of its comparison came from the same polluted tree. CI checks out clean, so it
never saw any of it and could not warn. The count went 44 to 43: a finding vanished
rather than a spurious one appearing, which is the direction nobody goes looking for.
THE CHANGE
One filter in
_paths, which is the only place this module globs: the declaration scan,the reference scan and both callsite corpora all come through it. Paths inside
.build,.swiftpm,DerivedData,build,.gradle,node_modulesorPodsare dropped.CI OUTPUT IS UNCHANGED BY CONSTRUCTION
CI already checks out clean, so it was always seeing the filtered numbers. This makes a
working tree agree with CI, not the other way round. Measured both ways on the same
commit:
polluted tree, filtered 266 / 234 / 1135 / 1353 / 2496
pristine checkout 266 / 234 / 1135 / 1353 / 2496
for SWIFT, KOTLIN, PRODUCTION, TEST and REFERENCE globs respectively.
THE INVARIANT IS TESTED, NOT ASSERTED
Five tests. The one that matters checks the safety property against git rather than
against my judgement: everything the filter drops is untracked, so no file the
repository actually contains can be hidden by it. Here that is 3,543 dropped and 0
tracked.
The others pin the exact shape that caused this (a dependency checkout whose own layout
puts
Sourcesunder.build), the second copy SwiftPM writes underindex-build,Gradle output for the day KSP emits Kotlin rather than Java, and a negative case:
buildis matched as a whole directory component only, so
Sources/buildsystem/build.swiftstays in the scan.
NOT IN THIS CHANGE
SWIFT_EXCLUDED_GLOBS and KOTLIN_EXCLUDED_GLOBS are still dead constants, referenced
nowhere but their own definitions. They document which packages are deliberately out of
scope and wiring them up would change what is scanned, so that belongs in its own
change with its own re-derive.
Governance suite 129 tests, 0 failures.